package com.kyd.push.common.utils;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Random;

/**
 * 在分布式系统中，需要生成全局UID的场合还是比较多的，twitter的snowflake解决了这种需求，实现也还是很简单的，除去配置信息，核心代码就是毫秒级时间41位+机器ID
 * 10位+毫秒内序列12位。 核心代码为其IdWorker这个类实现，其原理结构如下(每部分用-分开):
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截 得到的值，这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如程序IdWorker类的startTime属性）
 * 41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
 * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号，加起来刚好64位，为一个Long型
 * 这样的好处是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞（由datacenter和机器ID作区分），并且效率较高，经测试，snowflake每秒能够产生26万ID左右，完全满足需要
 *
 * from https://github.com/twitter/snowflake/blob/master/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
 * from https://github.com/adyliu/idcenter/blob/master/src/main/java/com/sohu/idcenter/IdWorker.java
 *
 * @author adyliu (imxylz@gmail.com)
 * @since 1.0
 */
public class IdWorker {
	private static final long startTime = 1524412800000L; // 定义一个起始时间 "2018-04-23";

	private final long workerId;
	private final long datacenterId;
	private final long idepoch;  // 时间起始标记点，作为基准，一般取系统的最近时间

	private static final long workerIdBits = 5L; // 机器标识占用的位数
	private static final long datacenterIdBits = 5L; //数据中心占用的位数
	private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
	private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

	private static final long sequenceBits = 12L; //毫秒内自增位, 序列号占用的位数
	private static final long workerIdShift = sequenceBits;
	private static final long datacenterIdShift = sequenceBits + workerIdBits;
	private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	private static final long sequenceMask = -1L ^ (-1L << sequenceBits);

	private long lastTimestamp = -1L;
	private long sequence;
	private static final Random r = new Random();

	public IdWorker() {
		this(startTime);
	}

	public IdWorker(long idepoch) {
		this(r.nextInt((int) maxWorkerId), r.nextInt((int) maxDatacenterId), 0, idepoch);
	}

	public IdWorker(long workerId, long datacenterId, long sequence) {
		this(workerId, datacenterId, sequence, startTime);
	}

	//
	public IdWorker(long workerId, long datacenterId, long sequence, long idepoch) {
		this.workerId = workerId;
		this.datacenterId = datacenterId;
		this.sequence = sequence;
		this.idepoch = idepoch;
		if (workerId < 0 || workerId > maxWorkerId) {
			throw new IllegalArgumentException("workerId is illegal: " + workerId);
		}
		if (datacenterId < 0 || datacenterId > maxDatacenterId) {
			throw new IllegalArgumentException("datacenterId is illegal: " + workerId);
		}
		if (idepoch >= System.currentTimeMillis()) {
			throw new IllegalArgumentException("idepoch is illegal: " + idepoch);
		}
	}

	public long getDatacenterId() {
		return datacenterId;
	}

	public long getWorkerId() {
		return workerId;
	}

	public long getTime() {
		return System.currentTimeMillis();
	}

	public long getId() {
		long id = nextId();
		return id;
	}

	public synchronized long nextId() {
		long timestamp = timeGen();
		if (timestamp < lastTimestamp) {
			throw new IllegalStateException("Clock moved backwards.");
		}
		if (lastTimestamp == timestamp) {  // 如果上一个timestamp与新产生的相等，则sequence加一(0-4095循环); 对新的timestamp，sequence从0开始
			sequence = (sequence + 1) & sequenceMask;
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);  // 重新生成timestamp
			}
		} else {
			sequence = 0;
		}
		lastTimestamp = timestamp;
		long id = ((timestamp - idepoch) << timestampLeftShift) // 时间戳部分
				| (datacenterId << datacenterIdShift)           // 数据中心部分
				| (workerId << workerIdShift)                   // 机器标识部分
				| sequence;                                     // 序列号部分
		return id;
	}

	/**
	 * get the timestamp (millis second) of id
	 * @param id the nextId
	 * @return the timestamp of id
	 */
	public long getIdTimestamp(long id){
		return idepoch + (id >> timestampLeftShift);
	}

	/**
	 * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后
	 * @param lastTimestamp
	 * @return
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	/**
	 * 获得系统当前毫秒数
	 * @return
	 */
	private long timeGen() {
		return System.currentTimeMillis();
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("IdWorker{");
		sb.append("workerId=").append(workerId);
		sb.append(", datacenterId=").append(datacenterId);
		sb.append(", idepoch=").append(idepoch);
		sb.append(", lastTimestamp=").append(lastTimestamp);
		sb.append(", sequence=").append(sequence);
		sb.append('}');
		return sb.toString();
	}

	private static class IdWorkerHolder {
		private static final IdWorker INSTANCE = new IdWorker();
	}

	public static IdWorker getInstance() {
		return IdWorkerHolder.INSTANCE;
	}

	/**
	 * Test
	 *
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println(System.currentTimeMillis());
		System.out.println(IdWorker.getInstance().getId());
		System.out.println(LocalDateTime.of(2018, 4,23,0,0,0).toInstant(ZoneOffset.of("+8")).toEpochMilli());
//
//
//		final long idepo = System.currentTimeMillis() - 3600 * 1000L;
//		IdWorker iw = new IdWorker(1, 1, 0, idepo);
//		IdWorker iw2 = new IdWorker(idepo);
//		for (int i = 0; i < 10; i++) {
//			System.out.println(iw.getId() + " -> " + iw2.getId());
//		}
//		System.out.println(iw);
//		System.out.println(iw2);
//		long nextId = iw.getId();
//		System.out.println(nextId);
//		long time = iw.getIdTimestamp(nextId);
//		System.out.println(time+" -> "+ new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(time)));
	}
}